package zhongbo.javaweb.instagram.web.controller;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import io.swagger.annotations.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import zhongbo.javaweb.instagram.dto.JsonRespDto;
import zhongbo.javaweb.instagram.util.JsonRespUtil;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 说明：
 * 通用的控制器，提供基本的CRUD操作
 *
 * @author zhongboWork
 * @version 1.0
 * @since 2022-01-04 10:30
 */
@Controller
@RequestMapping("Mongo/post")
@Api(value = "Mongo帖子相关接口", tags = {"Mongo帖子相关接口"})
@Slf4j
public class MongoPostController {

    @Autowired
    private MongoTemplate mongoTemplate;

    private final Map<String, MongoCollection<Document>> mongoCollectionMapCache = new ConcurrentHashMap<>();

    public static class MongoKey {
        public static final String primaryId = "id";
        public static final String md5 = "md5";
        public static final String mongoSet = "$set";
        public static final String collectName = "name";
        public static final String limit = "start";
        public static final String skip = "size";
    }

    @PostConstruct
    public void init() {
        MongoCollection<Document> test = mongoTemplate.getCollection("test");
        mongoCollectionMapCache.put("test", test);
    }

    @ResponseBody
    @RequestMapping(value = "/{name}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "添加或更新一条记录接口")
    public JsonRespDto<UpdateResult> addOrUpdateOne(
            @ApiParam(required = true, value = "文档") @RequestBody Document entity,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        Object id = entity.get(MongoKey.primaryId);
        JsonRespDto<UpdateResult> checkId = checkIdAndName(id, name);
        if (!JsonRespUtil.isSuccess(checkId)) {
            return checkId;
        }
        Bson bson = Filters.eq(MongoKey.primaryId, id);
        MongoCollection<Document> collection = getCollection(name);
        FindIterable<Document> documents = collection.find(bson);
        Document first = documents.first();
        Document edit = Document.parse(entity.toJson());
        UpdateResult updateResult;
        if (first == null) {
            log.info("addOne id {}", id);
            // @see <a href="https://blog.csdn.net/weixin_39031707/article/details/103622135">Invalid BSON field name xx 问题解决</a>
            updateTime(edit);
            Document insert = new Document(MongoKey.mongoSet, edit);
            updateResult = collection.updateOne(bson, insert, new UpdateOptions().upsert(true));
        } else {
            log.info("updateOne id {}", id);
            Document document = new Document(first);
            document.remove("_id");
            // 更新操作
            document.putAll(edit);
            updateTime(document);
            // 更新的时候要加"$set"
            // @see <a href="https://blog.csdn.net/weixin_39031707/article/details/103622135">Invalid BSON field name xx 问题解决</a>
            Document insert = new Document(MongoKey.mongoSet, document);
            updateResult = collection.updateOne(bson, insert, new UpdateOptions().upsert(false));
        }
        return new JsonRespDto<UpdateResult>().setData(updateResult).makeSuccess();
    }

    /**
     * 说明：
     *
     * @author zhongboNoteHome
     * @version 1.0
     * @since 2022-05-15 16:55
     */
    @ApiModel(value = "UpdateByConditionDto对象", description = "rest 条件批量更新结构模型")
    @Data
    public static class UpdateManyDto {

        @ApiModelProperty(value = "更新条件")
        private Document condition;

        @ApiModelProperty(value = "更新内容")
        private Document update;

    }

    @ResponseBody
    @RequestMapping(value = "/{name}/many", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "批量条件更新多条记录接口")
    public JsonRespDto<UpdateResult> updateMany(
            @ApiParam(required = true, value = "文档") @RequestBody UpdateManyDto entity,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        Document condition = entity.getCondition();
        Document update = entity.getUpdate();
        if (condition == null || condition.keySet().isEmpty()) {
            return new JsonRespDto<UpdateResult>().makeError().appendMsg("condition不能为空");
        }
        if (update == null || update.keySet().isEmpty()) {
            return new JsonRespDto<UpdateResult>().makeError().appendMsg("update不能为空");
        }
        MongoCollection<Document> collection = getCollection(name);
        Document document = new Document();
        document.put(MongoKey.mongoSet, update);
        UpdateResult updateResult = collection.updateMany(condition, document);
        return new JsonRespDto<UpdateResult>().setData(updateResult).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/many", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "批量添加或更新多条记录接口")
    public JsonRespDto<List<UpdateResult>> addOrUpdateMany(
            @ApiParam(required = true, value = "文档") @RequestBody List<Document> entityList,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (StringUtils.isEmpty(name)) {
            return new JsonRespDto<List<UpdateResult>>().makeError().appendMsg("未指定name");
        }
        if (entityList.isEmpty()) {
            return new JsonRespDto<List<UpdateResult>>().makeError().appendMsg("未指定内容");
        }
        // 批量添加或更新 entityList
        List<UpdateResult> updateResults = new ArrayList<>();
        for (Document entity : entityList) {
            Object id = entity.get(MongoKey.primaryId);
            JsonRespDto<UpdateResult> checkId = checkIdAndName(id, name);
            if (!JsonRespUtil.isSuccess(checkId)) {
                JsonRespDto<List<UpdateResult>> listJsonRespDto = new JsonRespDto<List<UpdateResult>>().setData(updateResults);
                listJsonRespDto.setCode(checkId.getCode());
                listJsonRespDto.setMsg(checkId.getMsg());
                return listJsonRespDto;
            }
        }
        for (Document document : entityList) {
            JsonRespDto<UpdateResult> updateOne = addOrUpdateOne(document, name);
            if (JsonRespUtil.isSuccess(updateOne)) {
                UpdateResult data = updateOne.getData();
                updateResults.add(data);
            } else {
                updateResults.add(updateOne.getData());
                JsonRespDto<List<UpdateResult>> listJsonRespDto = new JsonRespDto<List<UpdateResult>>().setData(updateResults);
                listJsonRespDto.setCode(updateOne.getCode());
                listJsonRespDto.setMsg(updateOne.getMsg());
                listJsonRespDto.appendMsg("更新失败, 第" + (entityList.indexOf(document) + 1) + "条");
                return listJsonRespDto;
            }
        }
        return new JsonRespDto<List<UpdateResult>>().setData(updateResults).makeSuccess();
    }

    private void updateTime(Document edit) {
        // 获取更新时间字段的列表
        List<String> updateTimeList = edit.getList("updateTimeList", String.class);
        if (!(updateTimeList instanceof ArrayList)) {
            updateTimeList = new ArrayList<>();
        }
        // 如果列表长度大于50个，则删除第一个
        if (updateTimeList.size() > 50) {
            updateTimeList.remove(0);
        }
        // 获取当前时间, Date对象类型
        Date currentTime = new Date();
        // 将当前时间添加到更新时间列表中
        updateTimeList.add(currentTime.toString());
        // 将更新时间列表添加到更新的文档中
        edit.put("updateTimeList", updateTimeList);
        Long createTime = edit.getLong("createTime");
        if (createTime == null) {
            edit.put("createTime", System.currentTimeMillis());
        }
        edit.put("updateTime", System.currentTimeMillis());
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "删除一条记录接口")
    public JsonRespDto<DeleteResult> delete(
            @PathVariable(MongoKey.primaryId) String id,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (StringUtils.isEmpty(id)) {
            return new JsonRespDto<DeleteResult>().makeError().appendMsg("未指定id");
        }
        Bson bson = Filters.eq(MongoKey.primaryId, id);
        DeleteResult deleteResult = getCollection(name).deleteOne(bson);
        long deletedCount = deleteResult.getDeletedCount();
        log.info("删除{}条记录,id={}", deletedCount, id);
        return new JsonRespDto<DeleteResult>().setData(deleteResult).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/delete", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "删除多个记录接口")
    public JsonRespDto<DeleteResult> deleteList(
            @RequestBody List<String> ids,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (ids.isEmpty()) {
            return new JsonRespDto<DeleteResult>().makeError().appendMsg("未指定id");
        }
        Bson bson = Filters.in(MongoKey.primaryId, ids);
        DeleteResult deleteResult = getCollection(name).deleteMany(bson);
        long deletedCount = deleteResult.getDeletedCount();
        log.info("删除{}条记录,ids={}", deletedCount, ids);
        return new JsonRespDto<DeleteResult>().setData(deleteResult).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/deleteMany", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "条件删除多个记录接口")
    public JsonRespDto<DeleteResult> deleteList(
            @RequestBody Document document,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (document == null) {
            return new JsonRespDto<DeleteResult>().makeError().appendMsg("未指定id");
        }
        if (document.isEmpty()) {
            return new JsonRespDto<DeleteResult>().makeError().appendMsg("未指定条件");
        }
        DeleteResult deleteResult = getCollection(name).deleteMany(document);
        long deletedCount = deleteResult.getDeletedCount();
        log.info("删除{}条记录,deleteResult={}", deletedCount, deleteResult);
        return new JsonRespDto<DeleteResult>().setData(deleteResult).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "查询一条记录接口")
    public JsonRespDto<Document> findOneById(
            @PathVariable(MongoKey.primaryId) String id,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (StringUtils.isEmpty(id)) {
            return new JsonRespDto<Document>().makeError().appendMsg("未指定id");
        }
        Bson bson = Filters.eq(MongoKey.primaryId, id);
        FindIterable<Document> documents = getCollection(name).find(bson).limit(1);
        Document first = documents.first();
        return new JsonRespDto<Document>().setData(first).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "分页条件查询记录接口")
    public JsonRespDto<List<Document>> findPage(
            @ApiParam(required = true, value = "查询数量(1~100)") int pageSize,
            @ApiParam(required = true, value = "查询页数(1~n)") int pageNumber,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (pageSize < 1) {
            pageSize = 1;
        }
        int maxPageSize = 100;
        if (pageSize > maxPageSize) {
            pageSize = maxPageSize;
        }
        int start = pageNumber > 0 ? (pageNumber - 1) * pageSize : 0;
        FindIterable<Document> limit = getCollection(name).find().limit(pageSize).skip(start);
        List<Document> results = new ArrayList<>();
        for (Document next : limit) {
            results.add(next);
        }
        return new JsonRespDto<List<Document>>().setData(results).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/{start}/{size}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "条件查询记录接口")
    public JsonRespDto<List<Document>> findPage(
            @ApiParam(required = true, value = "文档") @RequestBody Document document,
            @ApiParam(required = true, value = "位置,从1开始") @PathVariable(MongoKey.limit) int start,
            @ApiParam(required = true, value = "数量") @PathVariable(MongoKey.skip) int size,
            @ApiParam(required = true, value = "名称") @PathVariable(MongoKey.collectName) String name
    ) {
        if (size < 1) {
            size = 1;
        }
        int maxSize = 100;
        if (size > maxSize) {
            size = maxSize;
        }
        start--;
        if (start < 0) {
            start = 0;
        }
        FindIterable<Document> limit = getCollection(name).find(document).limit(size).skip(start);
        List<Document> results = new ArrayList<>();
        for (Document next : limit) {
            results.add(next);
        }
        return new JsonRespDto<List<Document>>().setData(results).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/count", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "查询记录总数接口")
    public JsonRespDto<Long> count(@PathVariable(MongoKey.collectName) String name) {
        long count = getCollection(name).estimatedDocumentCount();
        return new JsonRespDto<Long>().setData(count).makeSuccess();
    }

    @ResponseBody
    @RequestMapping(value = "/{name}/count", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ApiOperation(value = "条件查询记录总数接口")
    public JsonRespDto<Long> count(
            @ApiParam(required = true, value = "文档") @RequestBody Document document,
            @PathVariable(MongoKey.collectName) String name
    ) {
        long count = getCollection(name).countDocuments(document);
        return new JsonRespDto<Long>().setData(count).makeSuccess();
    }

    private synchronized MongoCollection<Document> getCollection(String name) {
        MongoCollection<Document> collection = mongoCollectionMapCache.get(name);
        if (collection == null) {
            collection = mongoTemplate.getCollection(name);
            mongoCollectionMapCache.put(name, collection);
        }
        return collection;
    }

    private JsonRespDto<UpdateResult> checkIdAndName(Object id, String name) {
        if (StringUtils.isEmpty(id) || StringUtils.isEmpty(name)) {
            return new JsonRespDto<UpdateResult>().makeError().appendMsg("未指定id 或 name");
        }
        if (!(id instanceof String)) {
            return new JsonRespDto<UpdateResult>().makeError().appendMsg("id必须为字符串");
        }
        String idTrim = String.valueOf(id).trim();
        String nameTrim = name.trim();
        if (StringUtils.isEmpty(idTrim) || StringUtils.isEmpty(nameTrim)) {
            return new JsonRespDto<UpdateResult>().makeError().appendMsg("id为空 或 name为空");
        }
        return new JsonRespDto<UpdateResult>().makeSuccess();
    }

}
